package com.huawei.boss.http;

import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import com.huawei.boss.monit.MonitTask;
import com.huawei.boss.monit.RunnableTask;

/**
 * 分流代理的思路
 * 
 * ps:尝试使用了netty来做,也是可以的，感觉也很方便。 
 * 
 * @author c00170351
 */
public class ShuntHttpServlet extends HttpServlet {
    private static final Logger LOG = LoggerFactory.getLogger(ShuntHttpServlet.class);
    private static final long serialVersionUID = 4008088059714394364L;
    private Set<String> gotoNGServnumbers;
    private MonitTask monitTask = new MonitTask();

    @Override
    public void init() throws ServletException {
        super.init();
        monitTask.setCheckInterval(60*1000L);
        //这里考虑了spring的Resource抽象便于spring集成。不使用这个的话，修改很简单
        //需要更加复杂通用的方案，可考虑quartz，这是后话了。
        monitTask.setResource(new ClassPathResource("servnumber.properties"));
        
        monitTask.setTask(new RunnableTask() {
            @Override
            public void run() throws Exception {
                //load special servnumbers from configuration or database
                gotoNGServnumbers = null;
            }
            
            @Override
            public String getName() {
                return "[分流号码同步]";
            }
        });
        //启用定时刷新
        monitTask.start();
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //get请求的肯定不是web service请求,因为请求体中不会有内容
        //通过httpclient直接先ocsif发送即可,并把结果写入resp对象中
        
        //TODO proxy请求需要增加超时参数
        HttpRequestProxy proxy = new HttpRequestProxy();
        
        //设置头部信息
        initHeader(req, proxy);
        proxy.setUrl("http://ip:port" + req.getRequestURI());
        String respData;
        try {
            respData = proxy.doRequest();
        } catch (Exception e) {
            //可能需要跳转到一个通用jsp页面去
            throw new ServletException(e);
        }
        resp.getWriter().write(respData);
    }
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //post请求的,可以考虑先判断请求路径，因为web service服务的路径都已经是已知的了。
        req.getQueryString();
        req.getRequestURL();
        
        //从inputstream中获取内容,得到一个String,这个正常的话就是soap协议报文
        String soapData = IOUtils.toString(req.getInputStream(), "UTF-8");//TODO 这里使用commons-io简单实现
        
        //判断报文是否是某些特殊号码的，有几种方法
        //1.可以考虑直接判断"标签+号码", 如<servnumber>13570230421</servnumber>，这个效率高，但要求ng的号码不能太多
        //2.用正则来判断,获取里边的号码来对比，和上面的差不多，就是号码的规则比较灵活
        //3.上面两个存在判断出错的可能(虽然几率非常小，特别是报文协议成熟的时候)，如果要完全准确，需解析整个xml，获取号码的位置
        //  这个就是完全的体力活了:(
        boolean gotoNG = false;
        if(soapData!=null)
        {
           //这里采用正则实现，根据不同的webservice，可以相应的正则对象预先保存起来
           Matcher matcher = Pattern.compile("<servnumber>(.*)</servnumber>").matcher(soapData);
           if(matcher.find())
           {
               String servnumber = matcher.group(1);
               LOG.debug(servnumber);
               //servnumber found, 这里使用全匹配
               if(gotoNGServnumbers.contains(servnumber))
               {
                   gotoNG = true;
                   //下面的ip地址和端口路由到NG
               }
           }
        }
        
        //判断是ocsif还是ngocsif之后，可以用httpclient发送soap请求,并把结果写入resp对象中
        //注意的是，需要把请求的头部透传过去~但host头部可能需要修改，否则可能校验不过
        //可以考虑参考web代理一样加上代理的头部信息，便于以后跟踪
        HttpRequestProxy proxy = new HttpRequestProxy();
        
        //设置头部信息
        initHeader(req, proxy);
        proxy.setUrl("http://ip:port" + req.getRequestURI());
        //httpclient的逻辑和get请求时应该是类似的,可以再抽取个私有方法 TODO
        proxy.setSoapData(soapData);
        String respData;
        try {
            //该方法会处理2xx,4xx,5xx等情况,我们这里也可以不考虑3xx等情况,甚至于cookie也是没什么用的
            //但是响应头部，响应代码等还是应该回去的。这里应该把httpclient响应的信息透传。TODO
            //servlet和httpclient的接口还是有些不一样的,要考虑再增加一个方法或者封装有关的响应信息而不只是body的内容
            respData = proxy.doRequest();
        } catch (Exception e) {
            //可能需要跳转到一个通用jsp页面去
            throw new ServletException(e);
        }
        resp.getWriter().write(respData);
    }
    
    @Override
    public void destroy() {
        //close the resources
        monitTask.stop();
        super.destroy();
    }
    
    private void initHeader(HttpServletRequest req, HttpRequestProxy proxy) {
        Map<String, String> header = new HashMap<String, String>();
        Enumeration<String> enums = req.getHeaderNames();
        if(enums!=null)
        {
            while(enums.hasMoreElements())
            {
                String headerName = enums.nextElement();
                //更改host以免有问题(外边有很多网站是会校验这个头部的)，如果没问题，这段可以去掉
                //TODO 基于安全性考虑，主动设置应该是无效的，这个需要验证一下
                if("HOST".equalsIgnoreCase(headerName))
                {
                    header.put(headerName, "ip:port");
                }
                else
                {
                    String headerValue = req.getHeader(headerName);
                    header.put(headerName, headerValue);
                }
            }
        }
        //考虑给HttpRequestProxy增加直接传递Enumeration参数的方法 TODO
        proxy.setHeader(header);
    }
}
